package cn.hl.hm.handler;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.vizhuo.utils.DateUtil;

import cn.hl.hm.constants.OrderStatus;
import cn.hl.hm.constants.PayMethod;
import cn.hl.hm.constants.UnionPayConfig;
import cn.hl.hm.entity.Coupon;
import cn.hl.hm.entity.Driver;
import cn.hl.hm.entity.Order;
import cn.hl.hm.entity.Push;
import cn.hl.hm.entity.UnifiedOrderRespose;
import cn.hl.hm.service.OrderService;
import cn.hl.hm.service.PushToAppIService;
import cn.hl.hm.util.NumberUtils;
import cn.hl.hm.util.unionpay.AcpService;
import cn.hl.hm.util.unionpay.DemoBase;
import cn.hl.hm.util.unionpay.LogUtil;
import cn.hl.hm.util.unionpay.SDKConfig;
import cn.hl.hm.util.unionpay.SDKConstants;
/**
 * 银联APP支付handler
 * 
 * @author 大和
 *
 */
@Service
public class UnionPayHandler extends PayHandler {

	private static final Logger logger = Logger.getLogger(UnionPayHandler.class);
	@Autowired
	private OrderService orderService;

	@Autowired
	@Qualifier("pushToAppServiceImpl")
	private PushToAppIService pushToAppIService;

	/**
	 * 处理银联支付请求
	 */
	@Override
	public Map<String, String> handler(Map<String, String> payMap) {
		SDKConfig.getConfig().loadPropertiesFromSrc();
		
		String orderCode = payMap.get("orderCode");
		String merId = payMap.get("merId");

		HashMap<String, String> map = new HashMap<String, String>(); 
		// 01 找订单 TODO
		Order toe = new Order();
		Order orderParam = new Order();
		orderParam.setOrderCode(orderCode);
		List<Order> selectOrder = orderService.selectOrder(orderParam);
		if(selectOrder!=null&&selectOrder.size()>0){
			toe = selectOrder.get(0);
		}else{
			return map;
		}
		
		Map<String, String> contentData = new HashMap<String, String>();
		
		/***银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改***/
		contentData.put("version", DemoBase.version);            //版本号 全渠道默认值
		contentData.put("encoding", DemoBase.encoding);     //字符集编码 可以使用UTF-8,GBK两种方式
		contentData.put("signMethod", SDKConfig.getConfig().getSignMethod());           		 	//签名方法 目前只支持01：RSA方式证书加密
		contentData.put("txnType", "01");              		 	//交易类型 01:消费
		contentData.put("txnSubType", "01");           		 	//交易子类 01：消费
		contentData.put("bizType", "000201");          		 	//填写000201
		contentData.put("channelType", "08");          		 	//渠道类型 08手机

		/***商户接入参数***/
		contentData.put("merId", merId);   		 				//商户号码，请改成自己申请的商户号或者open上注册得来的777商户号测试
		contentData.put("accessType", "0");            		 	//接入类型，商户接入填0 ，不需修改（0：直连商户， 1： 收单机构 2：平台商户）
		contentData.put("orderId", toe.getOrderCode());        	 	    //商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则	
		contentData.put("txnTime", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));		 		    //订单发送时间，取系统时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效
		contentData.put("accType", "01");					 	//账号类型 01：银行卡02：存折03：IC卡帐号类型(卡介质)
		contentData.put("txnAmt", NumberUtils.y2f(toe.getPayPrice() + "") + "");						//交易金额 单位为分，不能带小数点
		contentData.put("currencyCode", "156");                 //境内商户固定 156 人民币
		//contentData.put("reqReserved", "透传字段");              //商户自定义保留域，交易应答时会原样返回
		
		//后台通知地址（需设置为外网能访问 http https均可），支付成功后银联会自动将异步通知报文post到商户上送的该地址，【支付失败的交易银联不会发送后台通知】
		//后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
		//注意:1.需设置为外网能访问，否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码 
		//    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200或302，那么银联会间隔一段时间再次发送。总共发送5次，银联后续间隔1、2、4、5 分钟后会再次通知。
		//    5.后台通知地址如果上送了带有？的参数，例如：http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签，否则将会验签失败
		contentData.put("backUrl", SDKConfig.getConfig().getBackUrl());
		
		/**对请求参数进行签名并发送http post请求，接收同步应答报文**/
		Map<String, String> reqData = AcpService.sign(contentData,DemoBase.encoding);			 //报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。
		String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl();								 //交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
		Map<String, String> rspData = AcpService.post(reqData,requestAppUrl,DemoBase.encoding);  //发送请求报文并接受同步应答（默认连接超时时间30秒，读取返回结果超时时间30秒）;这里调用signData之后，调用submitUrl之前不能对submitFromData中的键值对做任何修改，如果修改会导致验签不通过
		
		/**对应答码的处理，请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
		//应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
		if(!rspData.isEmpty()){
			if(AcpService.validate(rspData, DemoBase.encoding)){
				LogUtil.writeLog("验证签名成功");
				String respCode = rspData.get("respCode") ;
				if(("00").equals(respCode)){
					//成功,获取tn号
					String tn = rspData.get("tn");
					map.put("tn", tn);
					//TODO
				}else{
					//其他应答码为失败请排查原因或做失败处理
					//TODO
				}
			}else{
				LogUtil.writeErrorLog("验证签名失败");
				//TODO 检查验证签名失败的原因
			}
		}else{
			//未返回正确的http状态
			LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
		}
		String reqMessage = DemoBase.genHtmlResult(reqData);
		String rspMessage = DemoBase.genHtmlResult(rspData);
		logger.info("......银联报文："+reqMessage+"              "+rspMessage);
		return map;
	
	}
	@Override
	protected void initHandler() {
		register(PayMethod.YL_PAY, UnionPayHandler.class);
	}

	/**
	 * 回调请求
	 */
	@Override
	public Boolean callback(HttpServletRequest req, HttpServletResponse resp) {
		boolean result = false;
		LogUtil.writeLog("......银联支付开始接收后台通知开始");
		
		String encoding = req.getParameter(SDKConstants.param_encoding);
		// 获取银联通知服务器发送的后台通知参数
		Map<String, String> reqParam = getAllRequestParam(req);

		LogUtil.printRequestLog(reqParam);

		Map<String, String> valideData = null;
		if (null != reqParam && !reqParam.isEmpty()) {
			Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
			valideData = new HashMap<String, String>(reqParam.size());
			while (it.hasNext()) {
				Entry<String, String> e = it.next();
				String key = (String) e.getKey();
				String value = (String) e.getValue();
				try {
					value = new String(value.getBytes(encoding), encoding);
					valideData.put(key, value);
					System.out.println("...银联解析中：  key_" +key +"    value_"+value);
					

				} catch (UnsupportedEncodingException e1) {
					e1.printStackTrace();
				}
			}
		}
		LogUtil.writeLog("......银联支付开始接收后台通知 -->解析后数据  ："+valideData);

		//重要！验证签名前不要修改reqParam中的键值对的内容，否则会验签不过
		if (!AcpService.validate(valideData, encoding)) {
			LogUtil.writeLog("验证签名结果[失败].");
			//验签失败，需解决验签问题
			result=  false;
			return result;
			
		} else {
			LogUtil.writeLog("验证签名结果[成功].");
			//【注：为了安全验签成功才应该写商户的成功处理逻辑】交易成功，更新商户订单状态
			
			String orderCode =valideData.get("orderId"); //获取后台通知的数据，其他字段也可用类似方式获取
			String respCode =valideData.get("respCode"); //获取应答码，收到后台通知了respCode的值一般是00，可以不需要根据这个应答码判断。
			String txnTime =valideData.get("txnTime"); //时间
			String signature =valideData.get("signature"); //签名
			String accNo =valideData.get("accNo"); //卡号
			String queryId =valideData.get("queryId"); //流水号

			
			
			UnifiedOrderRespose unifiedOrderRespose = new UnifiedOrderRespose();
			unifiedOrderRespose.setTime_end(txnTime);
			unifiedOrderRespose.setOut_trade_no(orderCode);
			unifiedOrderRespose.setSign(signature);
			unifiedOrderRespose.setOpenid(accNo);
			unifiedOrderRespose.setTransaction_id(queryId);
			LogUtil.writeLog("验证签名值  ：unifiedOrderRespose:"+unifiedOrderRespose.toString());

			result= processBusiness(unifiedOrderRespose);

		}
		LogUtil.writeLog("BackRcvResponse接收后台通知结束");
		//返回给银联服务器http 200  状态码
		try {
			resp.getWriter().print("ok");
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}
	/**
	 * 获取请求参数中所有的信息
	 * 
	 * @param request
	 * @return
	 */
	public static Map<String, String> getAllRequestParam(final HttpServletRequest request) {
		Map<String, String> res = new HashMap<String, String>();
		Enumeration<?> temp = request.getParameterNames();
		if (null != temp) {
			while (temp.hasMoreElements()) {
				String en = (String) temp.nextElement();
				String value = request.getParameter(en);
				res.put(en, value);
				//在报文上送时，如果字段的值为空，则不上送<下面的处理为在获取所有参数数据时，判断若值为空，则删除这个字段>
				//System.out.println("ServletUtil类247行  temp数据的键=="+en+"     值==="+value);
				if (null == res.get(en) || "".equals(res.get(en))) {
					res.remove(en);
				}
			}
		}
		return res;
	}
	private Boolean processBusiness(UnifiedOrderRespose unifiedOrderRespose) {
		logger.info("...回调开始处理业务");
		// 查订单
		Order orderParam = new Order();
		orderParam.setOrderCode(unifiedOrderRespose.getOut_trade_no());
		List<Order> selectOrder = orderService.selectOrder(orderParam);
		Order order = new Order();
		if (selectOrder != null && selectOrder.size() > 0) {
			order = selectOrder.get(0);

			if (StringUtils.isBlank(unifiedOrderRespose.getSign())) {// 签名为空
				return false;
			}
			if (order.getOrderStatus().equals(OrderStatus.TO_PAY.getStatus())) {
				String userPaymentTime = unifiedOrderRespose.getTime_end();// 支付时间
				logger.info("支付时间" + userPaymentTime);
				if (userPaymentTime != null && !("null".equals(userPaymentTime))) {

				} else {
					userPaymentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
				}
				Date string2Date = DateUtil.string2Date(userPaymentTime, "yyyyMMddHHmmss");
				logger.info("......转换后支付时间" + string2Date);
				// 1 修改订单
				Order orderUpParam = new Order();
				orderUpParam.setId(order.getId());
				orderUpParam.setOrderStatus(OrderStatus.TO_ARRIVE.getStatus());
				orderUpParam.setPayTime(string2Date);
				orderUpParam.setPaymentNo(unifiedOrderRespose.getOpenid());
				orderUpParam.setPayTransNo(unifiedOrderRespose.getTransaction_id());
				orderUpParam.setPayType(PayMethod.YL_PAY.getCode());
				int updateOrderById = orderService.updateOrderById(orderUpParam);
				if (updateOrderById > 0) {
					////////
					// 查询优惠券
					List<Coupon> tce = null;
					logger.info("银联回掉更改优惠券");
					if (StringUtils.isNotBlank(order.getPersonalCode())) {
						Map<String, String> map1 = new HashMap<>();
						Coupon cpParam = new Coupon();
						cpParam.setUserAccount(order.getSenderPhone());
						cpParam.setCouponCode(order.getPersonalCode());
						tce = orderService.selectCoupon(cpParam);
						if (tce != null & tce.size() > 0) {
							Coupon cpUpdateParam = new Coupon();
							cpUpdateParam.setId(tce.get(0).getId());
							cpUpdateParam.setStatus("1");
							cpUpdateParam.setUseTime(new Date());
							int updateCoupon = orderService.updateCoupon(cpUpdateParam);
							if (updateCoupon > 0) {
								return true;
							} else {
								return false;
							}
						} else {
							return true;
						}
					}
					// push to driver
					Driver driverSelectParam = new Driver();
					driverSelectParam.setId(order.getDriverId());

					List<Driver> driverList = orderService.selectDriver(driverSelectParam);
					if (driverList != null && driverList.size() > 0) {
						Driver driver = driverList.get(0);

						if (StringUtils.isNotBlank(driver.getDeviceToken())) {
							Push push = new Push();
							push.setPtype(1);
							push.setPushoid(order.getOrderCode());
							push.setContext("订单支付成功，已收到款项" + order.getPayPrice() + "元");// 要发送的内容
							push.setDisplayType("notification");// message:
																// 消息，通知：notification
							push.setOperationType(1);// 用户类型：1司机，2发货端
							push.setPushClient("driverPushClient");// 使用的推送Client
																		// 是往发货端：consignerPushClient
																		// 或
																		// 司机端发送：driverPushClient。
							push.setPushType("listcast");// unicast：单播
															// //
															// listcast：列表
							push.setTitle("订单支付成功"); // 发送的标题
							push.setTokenStr(new String[] { driver.getDeviceToken() }); // 要发送手机的token
							// ios & android推送公共调用方法
							pushToAppIService.pushCommon(push);
						}

					}

					return true;

				} else {
					return false;
				}

			} else {
				logger.info("......订单"+order.getOrderCode()+"...状态 "+order.getOrderStatus()+"...不处理 返回true");
				return true;
			}

		} else {
			return false;
		}
	}

}
